package org.airlink.persistence.criteria {
	import flash.utils.Dictionary;
	
	import flex.persistence.EntityManager;
	import flex.persistence.EntityTransaction;
	import flex.persistence.criteria.AbstractQuery;
	import flex.persistence.criteria.CriteriaQuery;
	import flex.persistence.criteria.Expression;
	import flex.persistence.criteria.Order;
	import flex.persistence.criteria.Predicate;
	import flex.persistence.criteria.Root;
	import flex.persistence.criteria.Selection;
	import flex.persistence.criteria.Subquery;
	import flex.persistence.metamodel.EntityType;
	
	import org.airlink.persistence.EntityManagerImpl;
	import org.airlink.persistence.EntityTypeImpl;
	
	public class CriteriaQueryImpl implements CriteriaQuery {
		protected var 	_entityManager:EntityManagerImpl;
		protected var 	_transaction:EntityTransaction;
		protected var	_criteriaBuilder:CriteriaBuilderImpl;
		protected var 	_resultClass:Class;
		protected var 	_roots:Array;
		protected var 	_selections:Array;	
		protected var 	_restrictions:Array;
		protected var 	_orders:Array;
		protected var 	_aliasIndex:uint;
		protected var 	_distinctVar:Boolean;
		protected var 	_groupsBy:Array;
		protected var 	_havings:Array;	
		protected var	_parameters:Array;
		
		
		
		public function CriteriaQueryImpl(resultClass:Class, entityManager:EntityManagerImpl, transaction:EntityTransaction, criteriaBuilder:CriteriaBuilderImpl) {
			_entityManager 	= entityManager;
			_transaction 	= transaction;
			_resultClass 	= resultClass;	
			_aliasIndex 	= 0;
			_distinctVar 	= false;
			_parameters 	= new Array();			
			_criteriaBuilder = criteriaBuilder;
		}	
		
		public function get entityManager():EntityManager {
			return _entityManager;
		}
		
		public function select(selections:Object):CriteriaQuery{
			if(selections is Selection) {
				if (_selections == null)
					_selections = new Array();
				_selections.push(selections);
			} else {
				if (_selections == null)
					_selections = new Array();
				for each (var selection:Selection in selections)
				_selections.push(selection);
			}			
			return this;
		}
		
		/**
		 * AbstractQuery<T> distinct(boolean distinct)
		 * Specify whether duplicate query results will be eliminated.
		 */
		public function distinct(distinct:Boolean):AbstractQuery {
			_distinctVar=distinct;
			return this;
		}
		/**
		 * <X> Root<X> from(java.lang.Class<X> entityClass)
		 * // <X> Root<X> from(EntityType<X> entity)
		 * Create and add a query root corresponding to the given entity, forming a cartesian product with any existing roots.
		 */
		public function from(entityClass:Class):Root {		
			var entityType:EntityType = new EntityTypeImpl(entityClass);
			var root:Root = new RootImpl(entityType,entityManager as EntityManagerImpl,_aliasIndex);
			EntityManagerImpl(entityManager).introspector.loadMetadata(entityClass);
			_aliasIndex++;
			addRoot(root);
			return root;
		}
		
		public function addRoot(root:Root):void{
			if(_roots == null)
				_roots = new Array();
			_roots.push(root);		
		}
		
		/**
		 * Predicate getRestriction()
		 * Return the predicate that corresponds to the where clause restriction(s), or null if no restrictions have been specified.
		 */
		public function get restrictions():Array {
			return _restrictions;
		}
		
		/**
		 * java.lang.Class<T> getResultType()
		 * Return the result type of the query or subquery.
		 */
		public function get resultType():Class {
			return _resultClass;
		}
		
		public function get transaction():EntityTransaction {
			return _transaction;
		}
		
		
		/**
		 * java.util.Set<Root<?>> getRoots()
		 * Return the query roots.
		 */
		public function get roots():Array {
			return _roots;
		}
		/**
		 * Selection<T> getSelection()
		 * Return the selection of the query, or null if no selection has been set.
		 */
		public function get selections():Array {
			return _selections;
		}
		
		/**
		 * AbstractQuery<T> groupBy(Expression<?>... grouping)
		 * AbstractQuery<T> groupBy(java.util.List<Expression<?>> grouping)
		 * Specify the expressions that are used to form groups over the query results.
		 */
		public function groupBy(groupsBy:Object):AbstractQuery {		
			
			if(groupsBy is Array){
				if (_groupsBy == null) 
					_groupsBy = new Array();
				
				for each (var pred:Predicate in groupsBy) {
					_groupsBy.push(pred);
					this.identifyParameters(pred as ExpressionImpl);
				}
							
			}
			else {
				if (_groupsBy == null) 
					_groupsBy = new Array();			
				_groupsBy.push(groupsBy);
				this.identifyParameters(groupsBy as ExpressionImpl);
			}
			return this;				
		}
		
		/**
		 * AbstractQuery<T>	having(Expression<java.lang.Boolean> restriction)
		 * Specify a restriction over the groups of the query.
		 * AbstractQuery<T> having(Predicate... restrictions)
		 * Specify restrictions over the groups of the query according the conjunction of the specified restriction predicates.
		 */
		public function having(havings:Object):AbstractQuery {
			if(havings is Array) {
				if(_havings == null) 
					_havings = new Array();
				
				for each (var pred:Predicate in havings) {
					_havings.push(pred);
					this.identifyParameters(pred as ExpressionImpl);
				}
			}
			else {
				if(_havings == null) _havings = new Array();
				_havings.push(havings);
				this.identifyParameters(havings as ExpressionImpl);
			}						
			return this;
		}
		/**
		 * boolean isDistinct()
		 * Return whether duplicate query results must be eliminated or retained.
		 */
		public function isDisctinct():Boolean {
			return _distinctVar;
		}
		/**
		 * <U> Subquery<U> subquery(java.lang.Class<U> type)
		 * Create a subquery of the query.
		 */
		public function subquery(type:Class):Subquery {
			return new SubQueryImpl(type,this,_criteriaBuilder);
		}
		/**
		 * AbstractQuery<T> where(Expression<java.lang.Boolean> restriction)
		 * Modify the query to restrict the query results according to the specified boolean expression.
		 * AbstractQuery<T>	where(Predicate... restrictions)
		 * Modify the query to restrict the query results according to the conjunction of the specified restriction predicates.
		 */
		public function where(restrictions:Object):AbstractQuery {				
			if(restrictions is Predicate){
				
				this.identifyParameters(restrictions as ExpressionImpl);								
				if (_restrictions == null) 
					_restrictions = new Array();			
				_restrictions.push(restrictions);			
			}
			else {
				if (_restrictions == null) 
					_restrictions = new Array();
				for each (var pred:Predicate in restrictions) {
					_restrictions.push(pred);
					this.identifyParameters(pred as ExpressionImpl);							
				}
			}
			return this;		
			
		}
		
		protected function identifyParameters(expression:ExpressionImpl):void{			
			var parameters:Array = expression.parameters;
			var parameter:Object;
			for (var key:String in parameters) {
				parameter = parameters[key];			
				if(!(parameter is Expression)) {			
					_parameters.push(parameter);								
					
				}
			}			
		}
		
		
		public function orderBy(orders:Object):CriteriaQuery {
			if(orders is Order) {
				if(_orders == null) _orders = new Array();
				_orders.push(orders);
			}
			else {
				if(_orders == null) 
					_orders = new Array();
				for each (var ord:Order in orders)
				_orders.push(ord);
			}	
			return this;
		}
		
		public function get orders():Array {
			return _orders;
		}
		
		public function get aliasIndex():uint {
			_aliasIndex++;
			return (_aliasIndex-1);
		}
		
		public function getGroupBy():Array{
			return _groupsBy;
		}		
		
		public function getHaving():Array{
			return _havings;
		}
		
		public function get parameters():Array{ 
			return _parameters;
		}
		
		public function get criteriaBuilder():CriteriaBuilderImpl {
			return _criteriaBuilder;
		}
	}
}